/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import DoubleFastBlockCompressor from './DoubleFastBlockCompressor';
import Long from '../util/long/index'
import Constants from './Constants'
import Util from './Util'
import { BlockCompressor, UNSUPPORTED } from './BlockCompressor'

export class StrategyInner {
    private compressor: BlockCompressor
    private ordinalValue: number

    constructor(ordinalValue, compressor: BlockCompressor) {
        this.ordinalValue = ordinalValue
        this.compressor = compressor;
    }

    getCompressor() {
        return this.compressor;
    }

    ordinal() {
        return this.ordinalValue
    }
}

export class Strategy {
    static FAST = new StrategyInner(0, UNSUPPORTED)
    static DFAST = new StrategyInner(1, new DoubleFastBlockCompressor())
    static GREEDY = new StrategyInner(2, UNSUPPORTED)
    static LAZY = new StrategyInner(3, UNSUPPORTED)
    static LAZY2 = new StrategyInner(4, UNSUPPORTED)
    static BTLAZY2 = new StrategyInner(5, UNSUPPORTED)
    static BTOPT = new StrategyInner(6, UNSUPPORTED)
    static BTULTRA = new StrategyInner(7, UNSUPPORTED)
}

export class CompressionParameters {
    private static MIN_HASH_LOG: number = 6;
    public static DEFAULT_COMPRESSION_LEVEL: number = 3;
    private static MAX_COMPRESSION_LEVEL: number = 22;
    private windowLog: number; // largest match distance : larger == more compression, more memory needed during decompression
    private chainLog: number; // fully searched segment : larger == more compression, slower, more memory (useless for fast)
    private hashLog: number; // dispatch table : larger == faster, more memory
    private searchLog: number; // nb of searches : larger == more compression, slower
    private searchLength: number; // match length searched : larger == faster decompression, sometimes less compression
    private targetLength: number; // acceptable match size for optimal parser (only) : larger == more compression, slower
    private StrategyInner: StrategyInner;
    private static DEFAULT_COMPRESSION_PARAMETERS: Array<Array<CompressionParameters>> = [[
                                                                                              // default
                                                                                              new CompressionParameters(19, 12, 13, 1, 6, 1, Strategy.FAST), /* base for negative levels */
                                                                                              new CompressionParameters(19, 13, 14, 1, 7, 0, Strategy.FAST), /* level  1 */
                                                                                              new CompressionParameters(19, 15, 16, 1, 6, 0, Strategy.FAST), /* level  2 */
                                                                                              new CompressionParameters(20, 16, 17, 1, 5, 1, Strategy.DFAST), /* level  3 */
                                                                                              new CompressionParameters(20, 18, 18, 1, 5, 1, Strategy.DFAST), /* level  4 */
                                                                                              new CompressionParameters(20, 18, 18, 2, 5, 2, Strategy.GREEDY), /* level  5 */
                                                                                              new CompressionParameters(21, 18, 19, 2, 5, 4, Strategy.LAZY), /* level  6 */
                                                                                              new CompressionParameters(21, 18, 19, 3, 5, 8, Strategy.LAZY2), /* level  7 */
                                                                                              new CompressionParameters(21, 19, 19, 3, 5, 16, Strategy.LAZY2), /* level  8 */
                                                                                              new CompressionParameters(21, 19, 20, 4, 5, 16, Strategy.LAZY2), /* level  9 */
                                                                                              new CompressionParameters(21, 20, 21, 4, 5, 16, Strategy.LAZY2), /* level 10 */
                                                                                              new CompressionParameters(21, 21, 22, 4, 5, 16, Strategy.LAZY2), /* level 11 */
                                                                                              new CompressionParameters(22, 20, 22, 5, 5, 16, Strategy.LAZY2), /* level 12 */
                                                                                              new CompressionParameters(22, 21, 22, 4, 5, 32, Strategy.BTLAZY2), /* level 13 */
                                                                                              new CompressionParameters(22, 21, 22, 5, 5, 32, Strategy.BTLAZY2), /* level 14 */
                                                                                              new CompressionParameters(22, 22, 22, 6, 5, 32, Strategy.BTLAZY2), /* level 15 */
                                                                                              new CompressionParameters(22, 21, 22, 4, 5, 48, Strategy.BTOPT), /* level 16 */
                                                                                              new CompressionParameters(23, 22, 22, 4, 4, 64, Strategy.BTOPT), /* level 17 */
                                                                                              new CompressionParameters(23, 23, 22, 6, 3, 256, Strategy.BTOPT), /* level 18 */
                                                                                              new CompressionParameters(23, 24, 22, 7, 3, 256, Strategy.BTULTRA), /* level 19 */
                                                                                              new CompressionParameters(25, 25, 23, 7, 3, 256, Strategy.BTULTRA), /* level 20 */
                                                                                              new CompressionParameters(26, 26, 24, 7, 3, 512, Strategy.BTULTRA), /* level 21 */
                                                                                              new CompressionParameters(27, 27, 25, 9, 3, 999, Strategy.BTULTRA) /* level 22 */
                                                                                          ],
    [
        // for size <= 256 KB
        new CompressionParameters(18, 12, 13, 1, 5, 1, Strategy.FAST), /* base for negative levels */
        new CompressionParameters(18, 13, 14, 1, 6, 0, Strategy.FAST), /* level  1 */
        new CompressionParameters(18, 14, 14, 1, 5, 1, Strategy.DFAST), /* level  2 */
        new CompressionParameters(18, 16, 16, 1, 4, 1, Strategy.DFAST), /* level  3 */
        new CompressionParameters(18, 16, 17, 2, 5, 2, Strategy.GREEDY), /* level  4.*/
        new CompressionParameters(18, 18, 18, 3, 5, 2, Strategy.GREEDY), /* level  5.*/
        new CompressionParameters(18, 18, 19, 3, 5, 4, Strategy.LAZY), /* level  6.*/
        new CompressionParameters(18, 18, 19, 4, 4, 4, Strategy.LAZY), /* level  7 */
        new CompressionParameters(18, 18, 19, 4, 4, 8, Strategy.LAZY2), /* level  8 */
        new CompressionParameters(18, 18, 19, 5, 4, 8, Strategy.LAZY2), /* level  9 */
        new CompressionParameters(18, 18, 19, 6, 4, 8, Strategy.LAZY2), /* level 10 */
        new CompressionParameters(18, 18, 19, 5, 4, 16, Strategy.BTLAZY2), /* level 11.*/
        new CompressionParameters(18, 19, 19, 6, 4, 16, Strategy.BTLAZY2), /* level 12.*/
        new CompressionParameters(18, 19, 19, 8, 4, 16, Strategy.BTLAZY2), /* level 13 */
        new CompressionParameters(18, 18, 19, 4, 4, 24, Strategy.BTOPT), /* level 14.*/
        new CompressionParameters(18, 18, 19, 4, 3, 24, Strategy.BTOPT), /* level 15.*/
        new CompressionParameters(18, 19, 19, 6, 3, 64, Strategy.BTOPT), /* level 16.*/
        new CompressionParameters(18, 19, 19, 8, 3, 128, Strategy.BTOPT), /* level 17.*/
        new CompressionParameters(18, 19, 19, 10, 3, 256, Strategy.BTOPT), /* level 18.*/
        new CompressionParameters(18, 19, 19, 10, 3, 256, Strategy.BTULTRA), /* level 19.*/
        new CompressionParameters(18, 19, 19, 11, 3, 512, Strategy.BTULTRA), /* level 20.*/
        new CompressionParameters(18, 19, 19, 12, 3, 512, Strategy.BTULTRA), /* level 21.*/
        new CompressionParameters(18, 19, 19, 13, 3, 999, Strategy.BTULTRA) /* level 22.*/
    ],
    [
        // for size <= 128 KB
        new CompressionParameters(17, 12, 12, 1, 5, 1, Strategy.FAST), /* base for negative levels */
        new CompressionParameters(17, 12, 13, 1, 6, 0, Strategy.FAST), /* level  1 */
        new CompressionParameters(17, 13, 15, 1, 5, 0, Strategy.FAST), /* level  2 */
        new CompressionParameters(17, 15, 16, 2, 5, 1, Strategy.DFAST), /* level  3 */
        new CompressionParameters(17, 17, 17, 2, 4, 1, Strategy.DFAST), /* level  4 */
        new CompressionParameters(17, 16, 17, 3, 4, 2, Strategy.GREEDY), /* level  5 */
        new CompressionParameters(17, 17, 17, 3, 4, 4, Strategy.LAZY), /* level  6 */
        new CompressionParameters(17, 17, 17, 3, 4, 8, Strategy.LAZY2), /* level  7 */
        new CompressionParameters(17, 17, 17, 4, 4, 8, Strategy.LAZY2), /* level  8 */
        new CompressionParameters(17, 17, 17, 5, 4, 8, Strategy.LAZY2), /* level  9 */
        new CompressionParameters(17, 17, 17, 6, 4, 8, Strategy.LAZY2), /* level 10 */
        new CompressionParameters(17, 17, 17, 7, 4, 8, Strategy.LAZY2), /* level 11 */
        new CompressionParameters(17, 18, 17, 6, 4, 16, Strategy.BTLAZY2), /* level 12 */
        new CompressionParameters(17, 18, 17, 8, 4, 16, Strategy.BTLAZY2), /* level 13.*/
        new CompressionParameters(17, 18, 17, 4, 4, 32, Strategy.BTOPT), /* level 14.*/
        new CompressionParameters(17, 18, 17, 6, 3, 64, Strategy.BTOPT), /* level 15.*/
        new CompressionParameters(17, 18, 17, 7, 3, 128, Strategy.BTOPT), /* level 16.*/
        new CompressionParameters(17, 18, 17, 7, 3, 256, Strategy.BTOPT), /* level 17.*/
        new CompressionParameters(17, 18, 17, 8, 3, 256, Strategy.BTOPT), /* level 18.*/
        new CompressionParameters(17, 18, 17, 8, 3, 256, Strategy.BTULTRA), /* level 19.*/
        new CompressionParameters(17, 18, 17, 9, 3, 256, Strategy.BTULTRA), /* level 20.*/
        new CompressionParameters(17, 18, 17, 10, 3, 256, Strategy.BTULTRA), /* level 21.*/
        new CompressionParameters(17, 18, 17, 11, 3, 512, Strategy.BTULTRA) /* level 22.*/
    ],
    [
        // for size <= 16 KB
        new CompressionParameters(14, 12, 13, 1, 5, 1, Strategy.FAST), /* base for negative levels */
        new CompressionParameters(14, 14, 15, 1, 5, 0, Strategy.FAST), /* level  1 */
        new CompressionParameters(14, 14, 15, 1, 4, 0, Strategy.FAST), /* level  2 */
        new CompressionParameters(14, 14, 14, 2, 4, 1, Strategy.DFAST), /* level  3.*/
        new CompressionParameters(14, 14, 14, 4, 4, 2, Strategy.GREEDY), /* level  4.*/
        new CompressionParameters(14, 14, 14, 3, 4, 4, Strategy.LAZY), /* level  5.*/
        new CompressionParameters(14, 14, 14, 4, 4, 8, Strategy.LAZY2), /* level  6 */
        new CompressionParameters(14, 14, 14, 6, 4, 8, Strategy.LAZY2), /* level  7 */
        new CompressionParameters(14, 14, 14, 8, 4, 8, Strategy.LAZY2), /* level  8.*/
        new CompressionParameters(14, 15, 14, 5, 4, 8, Strategy.BTLAZY2), /* level  9.*/
        new CompressionParameters(14, 15, 14, 9, 4, 8, Strategy.BTLAZY2), /* level 10.*/
        new CompressionParameters(14, 15, 14, 3, 4, 12, Strategy.BTOPT), /* level 11.*/
        new CompressionParameters(14, 15, 14, 6, 3, 16, Strategy.BTOPT), /* level 12.*/
        new CompressionParameters(14, 15, 14, 6, 3, 24, Strategy.BTOPT), /* level 13.*/
        new CompressionParameters(14, 15, 15, 6, 3, 48, Strategy.BTOPT), /* level 14.*/
        new CompressionParameters(14, 15, 15, 6, 3, 64, Strategy.BTOPT), /* level 15.*/
        new CompressionParameters(14, 15, 15, 6, 3, 96, Strategy.BTOPT), /* level 16.*/
        new CompressionParameters(14, 15, 15, 6, 3, 128, Strategy.BTOPT), /* level 17.*/
        new CompressionParameters(14, 15, 15, 8, 3, 256, Strategy.BTOPT), /* level 18.*/
        new CompressionParameters(14, 15, 15, 6, 3, 256, Strategy.BTULTRA), /* level 19.*/
        new CompressionParameters(14, 15, 15, 8, 3, 256, Strategy.BTULTRA), /* level 20.*/
        new CompressionParameters(14, 15, 15, 9, 3, 256, Strategy.BTULTRA), /* level 21.*/
        new CompressionParameters(14, 15, 15, 10, 3, 512, Strategy.BTULTRA) /* level 22.*/
    ]
    ];

    constructor(windowLog: number, chainLog: number, hashLog: number, searchLog: number, searchLength: number, targetLength: number, StrategyInner: StrategyInner) {
        this.windowLog = windowLog;
        this.chainLog = chainLog;
        this.hashLog = hashLog;
        this.searchLog = searchLog;
        this.searchLength = searchLength;
        this.targetLength = targetLength;
        this.StrategyInner = StrategyInner;
    }

    public getWindowLog(): number
    {
        return this.windowLog;
    }

    public getSearchLength(): number
    {
        return this.searchLength;
    }

    public getChainLog(): number
    {
        return this.chainLog;
    }

    public getHashLog(): number
    {
        return this.hashLog;
    }

    public getSearchLog(): number
    {
        return this.searchLog;
    }

    public getTargetLength(): number
    {
        return this.targetLength;
    }

    public getStrategy(): StrategyInner
    {
        return this.StrategyInner;
    }

    public static compute(compressionLevel: number, inputSize: number): CompressionParameters {
        let defaultParameters: CompressionParameters = CompressionParameters.getDefaultParameters(compressionLevel, Long.fromNumber(inputSize));

        let targetLength: number = defaultParameters.targetLength;
        let windowLog: number = defaultParameters.windowLog;
        let chainLog: number = defaultParameters.chainLog;
        let hashLog: number = defaultParameters.hashLog;
        let searchLog: number = defaultParameters.searchLog;
        let searchLength: number = defaultParameters.searchLength;
        let strategy: StrategyInner = defaultParameters.StrategyInner;

        if (compressionLevel < 0) {
            targetLength = -compressionLevel; // acceleration factor
        }

        let maxWindowResize: Long = Long.fromNumber(1).shiftLeft(Constants.MAX_WINDOW_LOG - 1);
        if (Long.fromNumber(inputSize).lessThan(maxWindowResize)) {
            let hashSizeMin: number = 1 << CompressionParameters.MIN_HASH_LOG;
            let inputSizeLog: number = (inputSize < hashSizeMin) ? CompressionParameters.MIN_HASH_LOG : Util.highestBit(inputSize - 1) + 1;
            if (windowLog > inputSizeLog) {
                windowLog = inputSizeLog;
            }
        }

        if (hashLog > windowLog + 1) {
            hashLog = windowLog + 1;
        }

        let cycleLog = Util.cycleLog(chainLog, strategy);
        if (cycleLog > windowLog) {
            chainLog -= (cycleLog - windowLog);
        }

        if (windowLog < Constants.MIN_WINDOW_LOG) {
            windowLog = Constants.MIN_WINDOW_LOG;
        }

        return new CompressionParameters(windowLog, chainLog, hashLog, searchLog, searchLength, targetLength, strategy);
    }

    private static getDefaultParameters(compressionLevel: number, estimatedInputSize: Long): CompressionParameters {
        let table: number = 0;

        if (!estimatedInputSize.eq(0)) {
            if (estimatedInputSize.lessThanOrEqual(16 * 1024)) {
                table = 3;
            } else if (estimatedInputSize.lessThanOrEqual(128 * 1024)) {
                table = 2;
            } else if (estimatedInputSize.lessThanOrEqual(256 * 1024)) {
                table = 1;
            }
        }

        let row: number = CompressionParameters.DEFAULT_COMPRESSION_LEVEL;

        if (compressionLevel != 0) { // TODO: figure out better way to indicate default compression level
            row = Math.min(Math.max(0, compressionLevel), CompressionParameters.MAX_COMPRESSION_LEVEL);
        }

        return CompressionParameters.DEFAULT_COMPRESSION_PARAMETERS[table][row];
    }
}
